<?php

namespace W3;

!defined('W3_ROOT_DIR') AND exit;

/**
 * HTML布局帮手
 *
 * @author edikud
 * @date 2022/10/22
 * @copyright Copyright (c) 2022 W3 (http://www.mcooo.com)
 * @license GNU General Public License 2.0
 */

class Element
{
    /**
     * 标签名称
     *
     * @access protected
     * @var string
     */
    protected $tag;

    /**
     * 表单属性列表
     *
     * @access protected
     * @var array
     */
    protected $attributes = [];

    /**
     * 是否闭合
     *
     * @access protected
     * @var boolean
     */
    protected $close;

    /**
     * 是否强制自闭合
     *
     * @access protected
     * @var boolean
     */
    protected $force;
	
    /**
     * 内部数据/元素列表
     *
     * @access protected
     * @var array
     */
    protected $elements = [];

    /**
     * 在元素之前设置内容
     * @var string
     */
    protected $append = [];

    /**
     * 在元素后设置内容
     * @var string
     */
    protected $preppend  = [];
	
    /**
     * Html 类名列表
     * @var array
     */
    protected $classes = [];
	
    /**
     * 样式属性列表
     *
     * @access protected
     * @var array
     */
    protected $styles = [];
	
    /**
     * 包裹元素列表
     *
     * @access protected
     * @var array
     */
    protected $wrap = [];
	
    /**
     * 创建新实例
     *
     * @access public
     * @return Element
     */
    public static function make(...$args): Element
    {
        return new static(...$args);
    }
	
    /**
     * @api
     *
     * @param string $tag
     */
    public function __construct(?string $tag = null, $content = null)
    {
        $this->tag = $tag;
		
        if ($content) {
            $this->set($content);
        }
    }

    /**
     * 包裹元素
     *
     * @param mixed $element 包裹被选元素的内容
     */
    public function wrap(Element $element) : Element
	{
		$this->wrap[] = $element;
        
        return $this;
    }

    /**
     * 在元素之前设置内容
     *
     * @param mixed $content 要附加的元素或字符串
     */
    public function append($content) : Element
	{
        $this->append[] = $content;
        return $this;
    }
    
    /**
     * 在元素后设置内容
     *
     * @param mixed $content 要预先放置的元素或字符串
     */
    public function preppend($content) : Element
	{
        $this->preppend[] = $content;
        return $this;
    }
	
    /**
     * 添加一个或多个 class 属性
     *
     * @param string $classes 类名或用空格分隔的不同类
     */
    public function addClass(?string $classes) : Element
	{
		if(is_null($classes)) {
			return $this;
		}
		
        foreach (explode(' ', $classes) as $class) 
		{
            $this->classes[$class] = $class;
        }
        return $this;
    }

    /**
     * 检查元素是否有类
     *
     * @param string $class Class name
     */
    public function hasClass(string $class) : bool 
	{
		return array_key_exists($class, $this->classes);
    }
	
    /**
     * 删除指定的类名
     * @param string $classes
     * @return static instance
     */
    public function removeClass(?string $classes) : Element
    {
		if(is_null($classes)) {
			return $this;
		}
		
        foreach (explode(' ', $classes) as $class) 
		{
            if(isset($this->classes[$class])) {
				$this->classes[$class] = null;
				unset($this->classes[$class]);
			}
        }
        return $this;
    }
	
    /**
     * 设置自定义数据
     *
     * @param string $name 属性名
     * @param string $value 规定属性的值(以字符串)
     */
    public function data(string $name, string $value) : Element
	{
        $this->attributes['data-' . $name] = $value;
        return $this;
    }
	
    /**
     * 设置自定义数据
     *
     * @param string $name 属性名
     * @param string $value 规定属性的值(以字符串)
     */
    public function removeData(string $name) : Element
	{
		if(isset($this->attributes['data-' . $name])) 
			
		    unset($this->attributes['data-' . $name]);
		
        return $this;
    }
	
    /**
     * 设置属性
     *
     * @param string $attribute
     * @param string|null $value
     *
     * @return $this
     */
    public function attribute(string $name, ?string $value = null) : Element
    {
        if ($name === 'class') {
            return $this->addClass($value);
        }
		
        if ($name === 'style') {
			return $this->style($value);
        }

        $this->attributes[$name] = $value;

        return $this;
    }

    /**
     * 设置多个属性
     *
     * @param string $attribute
     *
     * @return $this
     */
    public function attributes(array $attributes) : Element
    {
        foreach ($attributes as $name => $value) 
		{
            if ($name === 'class') {
                $this->addClass($value);
                continue;
            }

            if ($name === 'style') {
                $this->style($value);
                continue;
            }

            if (is_int($name)) {
                $name = $value;
                $value = '';
            }

            $this->attribute($name, (string) $value);
        }

        return $this;
    }
	
    /**
     * 去除属性
     *
     * @param string $name
     *
     * @return self
     */
    public function removeAttribute(string $name) : Element
    {
        if ($name === 'class') {
            $this->classes = [];

            return $this;
        }

        if ($name === 'style') {
            $this->styles = [];

            return $this;
        }

        if (array_key_exists($name, $this->attributes)) {
            unset($this->attributes[$name]);
        }
        return $this;
    }

    /**
     * 获得属性
     *
     * @param string $name
     *
     * @return string|null
     */
    public function getAttribute(string $name)
    {
        if ($name === 'class') {
            return implode(' ', $this->classes);
        }
		
        if ($name === 'style') {
            return implode('; ', $this->styles);
        }
		return $this->attributes[$name] ?? NULL;
    }

    /**
     * @param string $id
     *
     * @return static
     */
    public function id(?string $id) : Element
    {
        return $this->attribute('id', $id);
    }
	

    /**
     * 显示 HTML 元素
     *
     * @return Element
     */
    public function show() : Element
    {
        return $this->style('display:block');
    }
	
    /**
     * 隐藏 HTML 元素
     *
     * @return Element
     */
    public function hide() : Element
    {
        return $this->style('display:none');
    }	
	
	
    /**
     * title
     *
     * @param string    $title   The title for Element
     * @return $this;
     */
    public function title(?string $title) : Element
	{
        return $this->attribute('title', $title);
    }
	
    /**
     * 设置CSS属性
     * 允许在元素上使用内联CSS样式
     * 
     * 注:
     * 在HTML元素上内联CSS属性 (e.g., <p style=...>)
     * 应尽可能避免，因为这往往导致不必要的代码重复。此外，HTML元素上的内联CSS被阻止
     *
     * @param string    $style      style
     */
    public function style(?string $style = null) : Element
	{
		if(is_null($style)) {
			return $this;
		}

        foreach (explode(';', $style) as $style) 
		{
			if(!empty($style)){
			    list($property, $value) = explode(':', $style);
			    $this->styles[$property] = $value;
			}
        }

        return $this;
    }
	
	
    /**
     * 对元素进行条件变换.
     *
     * @param bool $condition
     * @param callable $callback
     */
    public function if(bool $condition, callable $callback) : Element
    {
        if ($condition) {
            $callback($this);
        }

        return $this;
    }
	
    /**
     * @api
     *
     * @param string $element
     *
     * @return Element
     */
    public function tag(string $tag) : Element
    {
        $this->tag = $tag;
        return $this;
    }

    /**
     * 设置是否自闭合
     *
     * @access public
     * @param boolean $close 是否自闭合
     * @return Element
     */
    public function close(bool $close) : Element
    {
        $this->force = !!$close;
        return $this;
    }

    /**
     * @api
     *
     * @param string $content
     */
    public function reset() : Element
    {
		$this->elements = [];
        return $this;
    }
	
    /**
     * 增加元素
     *
     * @access public
     * @param mixed  $element    Element object | String
     * @param $name name
     * @param string $after 在某个元素之后
     * @return Element
     */
    public function set($element, ?string $name = NULL, ?string $after = NULL) : Element
    {
		if (is_null($element)) {
            return $this;
        }
		
		if(NULL !== $after && isset($this->elements[$after])){
			!is_string($name) ? $this->elements[$after][] = $element : $this->elements[$after][$name] = $element;
		} else {
			!is_string($name) ? $this->elements[] = $element : $this->elements[$name][$name] = $element;
		}
		return $this;
    }
	
    /**
     * @api
     *
     * @return Element object | string | NULL
     */
    public function get(string $key = null)
    {
		return $this->elements[$key][$key] ?? NULL;
    }

    /**
     * @return string
     */
    public function render()
    {
        if (empty($this->elements)) {
            $this->close = true;
        }

        if (NULL !== $this->force) {
            $this->close = $this->force;
        }
		
		$preppend = '';
		$html = '';
		$append = '';
		
        foreach ($this->preppend as $element) $preppend .=  (string) $element;
		
        /** 输出标签 */
        $html .= $this->tag ? "<{$this->tag}" : '';

        /** 支持自闭合 */
        if ($this->tag) {
			
            if (count($this->classes)) {
				$this->attributes['class'] = implode(' ', $this->classes);
            }
			
            if (count($this->styles)) {
			    $css = null;
                foreach($this->styles as $key => $value) 
				{
                    $css .= $key . ': ' . $value . '; ';
                }
                $this->attributes['style'] = $css;
            }
			
            /** 输出属性 */
            foreach ($this->attributes as $name => $value) 
		    {
                $html .= (null === $value ? " {$name}" : " {$name}=\"{$value}\"");
            }
			$html .= $this->close ? " />\n" : ">";
        }
		
        foreach ($this->elements as $elements) 
		{
			if (is_array($elements)){
				foreach ($elements as $element) $html .=  (string) $element;
			} else {
                $html .=  (string) $elements;
			}
        }
		
        if ($this->tag && !$this->close) {
            $html .= "</{$this->tag}>\n";
        }
		
		if(!empty($this->wrap)){
			
			$tmp = null;
			foreach ($this->wrap as $last) 
			{
		        if(is_null($tmp)){
			        $tmp = $last->set($html);
		        } else {
			        $tmp = $last->set($tmp->render());
		        }
			}

			$html = $last->render();
		}

		foreach ($this->append as $element) $append .=  (string) $element;

		return $preppend . $html . $append;
    }
	
    public function  __toString() 
	{
		return $this->render();
	}	

}
